﻿/*
 * BlockBitmapSource
 * 
 * initial implementation : 3.12.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;

namespace Petra.Gis.ImagingServices.Tiff
{

  public partial class BlockBitmapSource : BitmapSource
  {


    #region BitmapSource CopyPixels


    /// <summary>
    ///     Requests pixels from this BitmapSource.
    /// </summary>
    /// <param name="pixels">
    ///     The destination array of pixels.
    /// </param>
    /// <param name="stride">
    ///     The stride of the destination array.
    /// </param>
    /// <param name="offset">
    ///     The starting index within the destination array to copy to.
    /// </param>
    /// <remarks>
    ///     Derived classes must override CopyPixelsCommon to implement
    ///     custom logic.
    /// </remarks>
    public sealed override void CopyPixels(Array pixels, int stride, int offset)
    {
      Int32Rect sourceRect = new Int32Rect(0, 0, this.PixelWidth, this.PixelHeight);
      CopyPixels(sourceRect, pixels, stride, offset);
    }

    /// <summary>
    ///     Requests pixels from this BitmapSource.
    /// </summary>
    /// <param name="sourceRect">
    ///     The rectangle of pixels to copy.
    /// </param>
    /// <param name="pixels">
    ///     The destination array of pixels.
    /// </param>
    /// <param name="stride">
    ///     The stride of the destination array.
    /// </param>
    /// <param name="offset">
    ///     The starting index within the destination array to copy to.
    /// </param>
    /// <remarks>
    ///     Derived classes must override CopyPixelsCommon to implement
    ///     custom logic.
    /// </remarks>
    public sealed override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
    {
      int elementSize;
      int bufferSize;
      Type elementType;
      ValidateArrayAndGetInfo(pixels, out elementSize, out bufferSize, out elementType);

      if (offset < 0)
      {
        throw new ArgumentOutOfRangeException("offset");
      }

      // We accept arrays of arbitrary value types - but not reference types.
      if (elementType == null || !elementType.IsValueType)
      {
        throw new ArgumentException("pixels");
      }

      checked
      {
        int offsetInBytes = offset * elementSize;
        if (offsetInBytes >= bufferSize)
        {
          throw new IndexOutOfRangeException();
        }

        // Get the address of the data in the array by pinning it.
        GCHandle arrayHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
        try
        {
          // Adjust the buffer and bufferSize to account for the offset.
          IntPtr buffer = arrayHandle.AddrOfPinnedObject();
          buffer = new IntPtr(((long)buffer) + (long)offsetInBytes);
          bufferSize -= offsetInBytes;

          CopyPixels(sourceRect, buffer, bufferSize, stride);
        }
        finally
        {
          arrayHandle.Free();
        }
      }
    }






    /// <summary>
    ///     Requests pixels from this BitmapSource.
    /// </summary>
    /// <param name="sourceRect">
    ///     The rectangle of pixels to copy.
    /// </param>
    /// <param name="buffer">
    ///     The destination buffer of pixels.
    /// </param>
    /// <param name="bufferSize">
    ///     The size of the buffer, in bytes.
    /// </param>
    /// <param name="stride">
    ///     The stride of the destination buffer.
    /// </param>
    /// <remarks>
    ///     Derived classes must override CopyPixelsCommon to implement
    ///     custom logic.
    /// </remarks>
    public sealed override void CopyPixels(Int32Rect sourceRect, IntPtr buffer, int bufferSize, int stride)
    {
      // WIC would specify NULL for the source rect to indicate that the
      // entire content should be copied.  WPF turns that into an empty
      // rect, which we inflate here to be the entire bounds.
      if (sourceRect.IsEmpty)
      {
        sourceRect.Width = PixelWidth;
        sourceRect.Height = PixelHeight;
      }

      if (sourceRect.X < 0)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if (sourceRect.Width < 0)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if ((sourceRect.X + sourceRect.Width) > PixelWidth)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if (sourceRect.Y < 0)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if (sourceRect.Height < 0)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if ((sourceRect.Y + sourceRect.Height) > PixelHeight)
      {
        throw new ArgumentOutOfRangeException("sourceRect");
      }

      if (buffer == IntPtr.Zero)
      {
        throw new ArgumentNullException("buffer");
      }

      checked
      {
        if (stride < 1)
        {
          throw new ArgumentOutOfRangeException("stride");
        }

        uint minStrideInBits = (uint)(sourceRect.Width * Format.BitsPerPixel);
        uint minStrideInBytes = ((minStrideInBits + 7) / 8);
        if (stride < minStrideInBytes)
        {
          throw new ArgumentOutOfRangeException("stride");
        }

        if (bufferSize < 1)
        {
          throw new ArgumentOutOfRangeException("bufferSize");
        }

        uint minBufferSize = (uint)((sourceRect.Height - 1) * stride) + minStrideInBytes;
        if (bufferSize < minBufferSize)
        {
          throw new ArgumentOutOfRangeException("bufferSize");
        }
      }

      CopyPixelsCore(sourceRect, stride, bufferSize, buffer);
    }





    protected virtual void CopyPixelsCore(Int32Rect sourceRect, int stride, int bufferSize, IntPtr buffer)
    {
      // ok image might be to big to fit into memory
      // select only those blocks which overlaps sourceRect

      if(this.Format.BitsPerPixel % 8 != 0)
        throw new ApplicationException("unsupported operation for pixel format non modulo of 8");

      int bytesPerPixel = this.Format.BitsPerPixel / 8;


      int xlo = sourceRect.X / this.TiffMetadata.BlockWidth;
      int xhi = (sourceRect.X + sourceRect.Width) / this.TiffMetadata.BlockWidth;

      int ylo = sourceRect.Y / this.TiffMetadata.BlockHeight;
      int yhi = (sourceRect.Y + sourceRect.Height) / this.TiffMetadata.BlockHeight;

      int blId = 0;

      IntPtr tileStartCol = buffer;
      IntPtr tileStartRow = buffer;

      for(int y = ylo; y <= yhi; y++)
      {
        for(int x = xlo; x <= xhi; x++)
        {
          //get block id
          blId = (this.TiffMetadata.NumBlocksHorizontal * y) + x;

          //copy block data into buff
          _blocks[blId].CopyPixels(sourceRect, tileStartCol, bufferSize, stride);

          // update buffer pointer...
          // new_pos = prev_pos + ( (block_xhi - rect_xlo) * bitsPerPixel)
          tileStartCol = (IntPtr)((long)tileStartRow + (((_blocks[blId].Bounds.X + _blocks[blId].Bounds.Width) - sourceRect.X) * bytesPerPixel));
          //return;
        }
        //return;
        // update buffer pointer
        // new_pos = prev_pos + ( (block_yhi - rect_ylo) * bitsPerPixel)
        tileStartRow = (IntPtr)((long)buffer + ((_blocks[blId].Bounds.Y + _blocks[blId].Bounds.Height) - sourceRect.Y) * stride);
        tileStartCol = tileStartRow;
      }
      

    }






    /// <summary>
    ///     Get the size of the specified array and of the elements in it.
    /// </summary>
    /// <param name="pixels">
    ///     The array to get info about.
    /// </param>
    /// <param name="elementSize">
    ///     On output, will contain the size of the elements in the array.
    /// </param>
    /// <param name="sourceBufferSize">
    ///     On output, will contain the size of the array.
    /// </param>
    /// <param name="elementType">
    ///     On output, will contain the type of the elements in the array.
    /// </param>
    private void ValidateArrayAndGetInfo(Array pixels,
                                         out int elementSize,
                                         out int sourceBufferSize,
                                         out Type elementType)
    {
      //
      // Assure that a valid pixels Array was provided.
      //
      if (pixels == null)
      {
        throw new ArgumentNullException("pixels");
      }

      if (pixels.Rank == 1)
      {
        if (pixels.GetLength(0) <= 0)
        {
          throw new ArgumentException("pixels");
        }
        else
        {
          checked
          {
            object exemplar = pixels.GetValue(0);
            elementSize = Marshal.SizeOf(exemplar);
            sourceBufferSize = pixels.GetLength(0) * elementSize;
            elementType = exemplar.GetType();
          }
        }

      }
      else if (pixels.Rank == 2)
      {
        if (pixels.GetLength(0) <= 0 || pixels.GetLength(1) <= 0)
        {
          throw new ArgumentException("pixels");
        }
        else
        {
          checked
          {
            object exemplar = pixels.GetValue(0, 0);
            elementSize = Marshal.SizeOf(exemplar);
            sourceBufferSize = pixels.GetLength(0) * pixels.GetLength(1) * elementSize;
            elementType = exemplar.GetType();
          }
        }
      }
      else
      {
        throw new ArgumentException("pixels");
      }
    }


    #endregion BitmapSource CopyPixels


  }
}
